/**
 *  A simple game engine using the Canvas.
 *  
 *  @author J. Hollingsworth
 */

package edu.elon.cs.mobile.game;

import java.util.concurrent.CopyOnWriteArrayList;

import com.badlogic.gdx.math.Vector2;
import com.badlogic.gdx.physics.box2d.Body;
import com.badlogic.gdx.physics.box2d.BodyDef;
import com.badlogic.gdx.physics.box2d.PolygonShape;
import com.badlogic.gdx.physics.box2d.World;

import android.content.Context;
import android.graphics.Canvas;
import android.graphics.Color;
import android.os.Bundle;
import android.os.Handler;
import android.os.Message;
import android.util.AttributeSet;
import android.view.Display;
import android.view.MotionEvent;
import android.view.SurfaceHolder;
import android.view.SurfaceView;
import android.view.WindowManager;
import android.widget.TextView;

public class GameView extends SurfaceView implements SurfaceHolder.Callback {

	private SurfaceHolder surfaceHolder;
	private TextView text;

	private GameThread thread;

	private World world;
	public static final float SCALE = 32.0f;
	
	private CopyOnWriteArrayList<Luma> lumas;

	public GameView(Context context, AttributeSet attrs) {
		super(context, attrs);

		// want to hear about changes to our surface
		surfaceHolder = getHolder();
		surfaceHolder.addCallback(this);
		
		WindowManager wm = (WindowManager) getContext().getSystemService(Context.WINDOW_SERVICE);
		Display display = wm.getDefaultDisplay();
		
		// sprites
		lumas = new CopyOnWriteArrayList<Luma>();

		// box2d
		Vector2 gravity = new Vector2(0.0f, 10.0f);
		world = new World(gravity, true);
		
		// this sets the ground
		BodyDef groundBodyDef = new BodyDef();
		groundBodyDef.position.set(new Vector2(0.0f, display.getHeight() / (SCALE + 6.0f)));
		Body groundBody = world.createBody(groundBodyDef);
		PolygonShape groundShapeDef = new PolygonShape();
		groundShapeDef.setAsBox(display.getWidth() / SCALE, 0.0f);
		groundBody.createFixture(groundShapeDef, 0.0f);

		// thread
		thread = new GameThread(new Handler() {
			public void handleMessage(Message m) {
				text.setText(m.getData().getString("text"));
			}
		});
	}

	public void setTextView(TextView tv) {
		this.text = tv;
	}
	
	@Override
	public boolean onTouchEvent(MotionEvent event) {
		
		if (event.getAction() == MotionEvent.ACTION_DOWN) {
			lumas.add(new Luma(event.getX(), event.getY(), world, getContext()));
		}
		
		return true;
	}

	/** GameView.GameThread **/
	private class GameThread extends Thread {

		private Handler msgHandler;

		private boolean isRunning;
		private long lastTime;

		// computing FPS
		private long nextUpdate;
		private int frames;

		public GameThread(Handler handler) {

			isRunning = false;

			// message handler
			msgHandler = handler;

			// computing FPS
			nextUpdate = System.currentTimeMillis() + 1000;
			frames = 0;
		}

		@Override
		public void run() {

			// 100ms lead-in time
			lastTime = System.currentTimeMillis() + 100;

			// main loop
			while (isRunning) {
				Canvas canvas = null;
				try {
					canvas = surfaceHolder.lockCanvas();
					synchronized (surfaceHolder) {
						// get the time
						long now = System.currentTimeMillis();
						double elapsed = (now - lastTime) / 1000.0;
						lastTime = now;

						// update the objects
						updatePhysics(elapsed);
						updateFPS(now);

						// draw the screen
						doDraw(canvas);
					}
				} finally {
					if (canvas != null) {
						surfaceHolder.unlockCanvasAndPost(canvas);
					}
				}
			}
		}

		private void doDraw(Canvas canvas) {
			canvas.drawColor(Color.BLACK);
			for (Luma luma : lumas) {
				luma.draw(canvas);
			}
		}

		private void updatePhysics(double elapsed) {
			world.step((float)elapsed, 6, 2);
			for (Luma luma : lumas) {
				luma.push();
				luma.update();
			}
		}

		private void updateFPS(long now) {
			// update FPS
			float fps = 0.0f;
			++frames;
			float overtime = now - nextUpdate;
			if (overtime > 0) {
				fps = frames / (1 + overtime/1000.0f);
				frames = 0;
				nextUpdate = System.currentTimeMillis() + 1000;

				Message msg = msgHandler.obtainMessage();
				Bundle b = new Bundle();
				b.putString("text", "fps: " + (int)fps);
				msg.setData(b);
				msgHandler.sendMessage(msg);
			}
		}

		public void setRunning(boolean b) {
			isRunning = b;
		}

	}


	@Override
	public void surfaceCreated(SurfaceHolder holder) {
		thread.setRunning(true);
		thread.start();
	}

	@Override
	public void surfaceDestroyed(SurfaceHolder holder) {
		boolean retry = true;
		thread.setRunning(false);
		while (retry) {
			try {
				thread.join();
				retry = false;
			}
			catch (InterruptedException ex) { 
			}
		}
	}

	@Override
	public void surfaceChanged(SurfaceHolder holder, int format, int width, int height) {}

}
